/**
 * @file factoryComm.c
 * @brief Funzioni per gestire input e output dell'utente.
 *
 * Scenario:
 * - un programma in esecuzione gestisce le thread e l'invocazione dei vari comandi.
 * - dei programmi figli vengono creati e inviano i loro input e output al programma padre
 * Conseguenze:
 * - si deve definire una modalità di interazione tra processo padre e processo figlio.
 * - il processo figlio deve prendersi la responsabilità di gestire l'interazione con l'utente.
 * - il processo padre deve occuparsi della registrazione dei vari processi figli
 * - il processo padre deve occuparsi dei dati in lettura/scrittura provenienti dai vari figli
 * Scelte:
 * - bisogna scegliere i meccanismi di sistema da utilizzare per permettere la comunicazione tra le varie entità
 * - è necessario definire i tipi di dati da utilizzare e le API da richiamare.
 * Design pattern utilizzati:
 * - Observer
 * - Factory
 *
 * @date Mar 5, 2012
 * @author Francesco Corvino
 */
#include "factoryComm.h"		/** libreria che specifica l'interfaccia degli oggetti utilizzati */
#include "std/managerStdio.h" 	/** libreria che specifica la concrete factory di default */
#include "../logproxy/logproxy.h" /** libreria per il logging */

/**
 * @brief Informazioni di configurazione delle comunicazioni.
 */
static FactoryComm _factComm_config;

/**
 * @brief Variabile statica contenente le informazioni sulla sessione di default.
 */
static HostSession _factComm_defSession;

/**
 * @brief Variabile che include tutte le sessioni registate.
 */
static CMap _factComm_sessions;

/**
 * @brief Funzione getter delle sessioni.
 *
 * @return restituisce le sessioni in memoria.
 */
CMap * FactComm_getSessions(){
	return &_factComm_sessions;
}

HostSession * FactComm_getSession(int key){
	return (HostSession*) CMap_get(&_factComm_sessions,key);
}

/**
 * @brief Aggiunge una nuova sessione in memoria.
 * @param addSession sessione da aggiungere.
 */
void _factComm_registerSession(HostSession * addSession){
	CMap_set(&_factComm_sessions,
		addSession->keySession,addSession,
		sizeof(HostSession));
	log_namedlevel(LOG_FACTORYCOMM_NAME,LOG_INFO_LEVEL,
			"Aggiunta una nuova sessione in memoria");
}

/**
 * @brief Rimuove una sessione dalla memoria occupandosi di chiuderne il canale.
 *
 * Se la sessione non può essere chiusa (cioè quando il puntatore alla funzione close è NULL)
 * la funzione non elimina la sessione dalla memoria.
 *
 * @param delSession Sessione da rimuovere.
 */
void FactComm_deleteSession(HostSession * delSession){
	log_namedlevel(LOG_FACTORYCOMM_NAME,LOG_INFO_LEVEL,
			"Rimuovo la sessione %d dalla memoria", delSession->keySession);
	HiddenType* sessionData = &(delSession->dataSession);
	if(delSession->close!=NULL){ // la sessione non può essere eliminata
		// richiede la chiusura della sessione per la concrete factory
		(*delSession->close)(sessionData);
		// libera la memoria allocata per la sessione
		CMap_remove(&_factComm_sessions, delSession->keySession);
	} else {
		log_namedlevel(LOG_FACTORYCOMM_NAME,LOG_DEBUG_LEVEL,
			"Si è cercato di rimuovere la sessione %d dalla memoria, l'operazione non è consentita",
			delSession->keySession);
	}
}

/**
 * @brief Funzione privata per gestire l'input/output dallo standard.
 *
 * @param factcomm puntatore al quale assegnare la concrete factory che
 * gestisce stdin - stdout.
 */
void _factComm_defaultFactory(ConcreteFactoryComm *factcomm) {
	MngStd_getFactoryDesc(factcomm);
}

/**
 * @brief Permette di inizializzare un puntatore a un oggetto FactoryComm.
 *
 * Il puntatore viene inizializzato con i valori di default:
 * - abilitazione della comunicazione di default tramite lo stdin e stdout.
 * - inizializzazione dell'array che dovrà contenere le concrete factory per la creazione delle sessioni.
 *
 * @param config puntatore alla variabile da inizializzare.
 */
void FactComm_init(FactoryComm * config) {
	config->enableDefaultComm = 1;
	DynArray_init(&(config->factories),NULL,sizeof(ConcreteFactoryComm));
	CMap_init(&_factComm_sessions);
}

/**
 * @brief Inizializza le comunicazioni con l'utente.
 *
 * @param config puntatore alla variabile contenente le informazioni sulle configurazioni da impostare.
 */
void FactComm_start(FactoryComm * config) {
	if(config->enableDefaultComm) {
		// ricava le informazioni sulla concrete factory di default
		ConcreteFactoryComm defFactory;
		_factComm_defaultFactory(&defFactory);
		// crea la comunicazione di default con lo stdin-stdout
		HostSession *stdSession = &_factComm_defSession;
		Session_init(stdSession,defFactory.getReadFD, defFactory.getWriteFD,
				defFactory.update, defFactory.close);
		// registra la nuova sessione
		_factComm_registerSession(stdSession);
	}

	// inizializza l'ascolto per le altre tipologie di comunicazioni
	if(config->factories.numelement>0){
		DynArray* arr = &(config->factories);
		int i = 0;
		ConcreteFactoryComm *cf;
		DYNARRAY_FOREACH(arr,&i,cf){
			log_namedlevel(LOG_FACTORYCOMM_NAME,LOG_INFO_LEVEL,
					"Configuro una concrete factory per le comunicazioni dell'host");
			ConfigurationComm* configCf = malloc(sizeof(ConfigurationComm));
			if(configCf==NULL){
				log_namedlevel(LOG_FACTORYCOMM_NAME,LOG_ERROR_LEVEL,
						"Non riesco ad aggiungere tutte le configurazioni per le comunicazioni");
				continue;
			}
			configCf->closeSession = FactComm_deleteSession;
			configCf->registerSession = _factComm_registerSession;
			(*(cf->configure))(configCf);
			(*(cf->startListen))();
		}
	}

	// memorizza staticamente le configurazioni effettuate
	memcpy(&_factComm_config,config,sizeof(FactoryComm));
}

/**
 * @brief effettua una ricerca della sessione per un determinata thread.
 *
 * Se la ricerca fallisce.. la thread viene assegnata alla sessione dello standard.
 *
 * @param tid thread id da ricercare per identificare il thread a una sessione.
 * @return la sessione utilizzata dalla thread con il tid specificato o la sessione di default.
 */
HostSession * FactComm_getSessionFor(pthread_t tid){
	CMap *arr = FactComm_getSessions();
	if(arr!=NULL){
		int iSession, num = CMap_size(arr);
		for(iSession=0;iSession<num;iSession++){
			HostSession *session = CMap_getByIndex(arr,iSession);
			DynArray *arrEl = &(session->threadsForSession);
			ElaborationEntity tmpElab; tmpElab.keyElaboration = tid;
			ElaborationEntity* ret = (ElaborationEntity*) DynArray_get(arrEl,&tmpElab);
			if(ret!=NULL) {
				return session;
			}
		}
	}
	return &_factComm_defSession;
}
